import { Event, Uri } from "vscode";
import { IAsyncIterableIterator } from "../../common/utils/async";
import { EQEnvInfo, EQEnvKind, EQEnvSource } from "./info";
import { BasicEQEnvsChangedEvent, EQEnvCollectionChangedEvent, EQEnvsChangedEvent, EQEnvsWatcher, IEQEnvsWatcher } from "./watcher";

export type BasicEnvInfo = {
    kind: EQEnvKind;
    executablePath: string;
    source?: EQEnvSource[];
    envPath?: string;
};

interface IResolver {

    resolveEnv(path: string): Promise<EQEnvInfo | undefined>;
}

export interface IResolvingLocator<I = EQEnvInfo> extends IResolver, ILocator<I> { }

export type EQEnvUpdatedEvent<I = EQEnvInfo> = {
    /**
     * The iteration index of The env info that was previously provided.
     */
    index: number;
    /**
     * The env info that was previously provided.
     */
    old?: I;
    /**
     * The env info that replaces the old info.
     * Update is sent as `undefined` if we find out that the environment is no longer valid.
     */
    update: I | undefined;
};


export interface IDiscoveryAPI {
    getEnvs(query?: EQLocatorQuery): EQEnvInfo[];
    triggerRefresh(query?: EQLocatorQuery & { clearCache?: boolean }, trigger?: 'auto' | 'ui'): Promise<void>;
    readonly onChanged: Event<EQEnvCollectionChangedEvent>;
    readonly onRefreshStart: Event<void>;
}

type BasicEQLocatorQuery = {
    /**
     * If provided, results should be limited to these env
     * kinds; if not provided, the kind of each environment
     * is not considered when filtering
     */
    kinds?: EQEnvKind[];
};

/**
 * The portion of a query related to env search locations.
 */
type SearchLocations = {
    /**
     * The locations under which to look for environments.
     */
    roots: Uri[];
    /**
     * If true, only query for workspace related envs, i.e do not look for environments that do not have a search location.
     */
    doNotIncludeNonRooted?: boolean;
};

export type EQLocatorQuery = BasicEQLocatorQuery & {
    /**
     * If provided, results should be limited to within these locations.
     */
    searchLocations?: SearchLocations;
};

export interface IEQEnvsIterator<I = EQEnvInfo> extends IAsyncIterableIterator<I> {
    /**
     * Provides possible updates for already-iterated envs.
     *
     * Once there are no more updates, `null` is emitted.
     *
     * If this property is not provided then it means the iterator does
     * not support updates.
     */
    onUpdated?: Event<EQEnvUpdatedEvent<I> | null>;
}

type QueryForEvent<E> = E extends EQEnvsChangedEvent ? EQLocatorQuery : BasicEQLocatorQuery;

export interface ILocator<I = EQEnvInfo, E extends BasicEQEnvsChangedEvent = EQEnvsChangedEvent>
    extends IEQEnvsWatcher<E> {
    iterEnvs(query?: QueryForEvent<E>): IEQEnvsIterator<I>;
}
interface IEmitter<E extends EQEnvsChangedEvent> {
    fire(e: E): void;
}
abstract class LocatorBase<I = EQEnvInfo, E extends BasicEQEnvsChangedEvent = EQEnvsChangedEvent>
    implements ILocator<I, E> {
    public readonly onChanged: Event<E>;

    protected readonly emitter: IEmitter<E>;

    constructor(watcher: IEQEnvsWatcher<E> & IEmitter<E>) {
        this.emitter = watcher;
        this.onChanged = watcher.onChanged;
    }

    // eslint-disable-next-line class-methods-use-this
    public abstract iterEnvs(query?: QueryForEvent<E>): IEQEnvsIterator<I>;
}

export abstract class Locator<I = EQEnvInfo> extends LocatorBase<I> {
    constructor() {
        super(new EQEnvsWatcher());
    }
}